package com.chinaoly.compiler;


import com.chinaoly.annotation.BindViewModel;
import com.chinaoly.annotation.ViewType;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;


@AutoService(Processor.class)
public class ViewModelProcessor extends AbstractProcessor {
    private Messager messager;
    Filer filer;
    JavaFile javaFile;
    private Elements mElementUtils;


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add(BindViewModel.class.getCanonicalName());
        return annotataions;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /*@Override
    public Set<String> getSupportedOptions() {
        Set<String> options = new LinkedHashSet<String>();
        options.add(CUSTOM_ANNOTATION);
        return options;
    }*/

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();


    }


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {


        if (javaFile != null) {
            messager.printMessage(Diagnostic.Kind.NOTE, "processViewModel:javaFile created");
            return false;
        }
        Set<? extends Element> viewModelSet = roundEnv.getElementsAnnotatedWith(BindViewModel.class);
        if (viewModelSet.size() <= 0) {
            messager.printMessage(Diagnostic.Kind.NOTE, "processViewModel:no viewModel found");
            return false;
        }
        processViewModel(annotations, roundEnv);

        return false;

    }

    /**
     * 处理生成的viewmodel注解
     *
     * @param annotations
     * @param roundEnv
     */
    private void processViewModel(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.NOTE, "processViewModel:create class 'ViewModelFactory' process");
        String activityName = "android.support.v4.app.FragmentActivity";
        String fragmentName = "android.support.v4.app.Fragment";

        String superName = "android.arch.lifecycle.ViewModelProvider.NewInstanceFactory";
        String packageName = "com.ch.vm.factory";

        String appName = "android.app.Application";

        TypeVariableName genericT = TypeVariableName.get("T", ClassName.bestGuess(
                "android.arch.lifecycle.ViewModel"));
        TypeVariableName paraT = TypeVariableName.get("Class<T>", Class.class);
        ParameterSpec viewModelPara = ParameterSpec.builder(paraT, "className")
                .addModifiers(Modifier.FINAL)

                .build();

        MethodSpec.Builder createM = MethodSpec
                .methodBuilder("create")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(viewModelPara)
                .returns(genericT)
                .addTypeVariable(genericT);


        List<MethodSpec> methodSpecList = new ArrayList<>();
        for (Element typeElement : roundEnv.getElementsAnnotatedWith(BindViewModel.class)) {//set集合都是注解节点，但是我们要拿到的是被注解的节点

            //   packageName =mElementUtils.getPackageOf()
            // typeElement.getEnclosingElement()
            //  System.out.print(typeElement.getSimpleName());
            // typeElement.
            //typeElement.asType().toString();
            TypeElement tp = (TypeElement) typeElement;
            BindViewModel viewModel = tp.getAnnotation(BindViewModel.class);

            String simpleName;
            TypeName typeName;
            String classSimpleName = tp.getSimpleName().toString();


            Enum viewType;
            viewType = viewModel.viewType();
            typeName = ClassName.bestGuess(tp.getQualifiedName().toString());
            simpleName = "obtain" + tp.getSimpleName();

            //messager.printMessage(Diagnostic.Kind.NOTE, "processViewModel:add method:" + simpleName);


            String paraValue1 = "mActivity";
            String paraValue2 = "mFragment";

            ParameterSpec mActivityPara = ParameterSpec
                    .builder(ClassName.bestGuess(activityName), paraValue1)
                    .build();
            ParameterSpec mFragmentPara = ParameterSpec
                    .builder(ClassName.bestGuess(fragmentName), paraValue2)
                    .build();
            // ParameterSpec spec = isFragment ?  : mActivityPara;

            MethodSpec.Builder builder1 = MethodSpec.methodBuilder(simpleName)
                    .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                    .returns(typeName).addParameter(mActivityPara);
            MethodSpec.Builder builder2 = MethodSpec.methodBuilder(simpleName)
                    .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                    .addParameter(mFragmentPara)
                    .returns(typeName);

            if (ViewType.ACTIVITY.equals(viewType) || ViewType.BOTH.equals(viewType)) {
                MethodSpec methodActivity =
                        builder1
                                .addStatement(" ViewModelFactory factory = ViewModelFactory.getInstance(" + paraValue1 + ".getApplication())")
                                .addStatement("return $T.of(" + paraValue1 + ", factory).get($N.class)", ClassName.bestGuess("android.arch.lifecycle.ViewModelProviders"),
                                        classSimpleName)
                                .build();

                methodSpecList.add(methodActivity);
            }
            if (ViewType.FRAGMENT.equals(viewType) || ViewType.BOTH.equals(viewType)) {
                MethodSpec methodFragment =
                        builder2
                                .addStatement(" ViewModelFactory factory = ViewModelFactory.getInstance(" + paraValue2 + ".getActivity().getApplication())")
                                .addStatement("return $T.of(" + paraValue2 + ", factory).get($N.class)", ClassName.bestGuess("android.arch.lifecycle.ViewModelProviders"),
                                        classSimpleName)
                                .build();
                methodSpecList.add(methodFragment);
            }


            // typeElement.getSimpleName()

            createM
                    .addStatement("if (className.isAssignableFrom($N.class)) {\n" +
                            "return (T)new $N (mApplication);}", classSimpleName, classSimpleName
                    );

        }


        String clzName = packageName + ".ViewModelFactory";

        FieldSpec mApplication = FieldSpec.builder(ClassName.bestGuess(appName), "mApplication")
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
                .build();
        ParameterSpec appPara = ParameterSpec.builder(ClassName.bestGuess(appName), "mApplication")
                .addModifiers(Modifier.FINAL)
                .build();
        MethodSpec constructor = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PRIVATE)
                .addParameter(appPara)
                .addStatement("this.$N = $N", "mApplication", "mApplication")
                .build();
        MethodSpec initInstance = MethodSpec.methodBuilder("getInstance")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addParameter(appPara)
                .returns(ClassName.bestGuess(clzName))
                .addStatement("if (INSTANCE == null) {\n" +
                        "            synchronized (ViewModelFactory.class) {\n" +
                        "                if (INSTANCE == null) {\n" +
                        "                    INSTANCE = new ViewModelFactory(mApplication);\n" +
                        "                }\n" +
                        "            }\n" +
                        "        }\n" +
                        "        return INSTANCE", "mApplication", "mApplication")
                .build();
        ClassName superinterface = ClassName.bestGuess(superName);
        messager.printMessage(Diagnostic.Kind.NOTE, "processViewModel:add constructor");


        messager.printMessage(Diagnostic.Kind.NOTE, "processViewModel:override method 'create'");

        ClassName claz = ClassName.bestGuess(clzName);
        FieldSpec instance = FieldSpec.builder(claz, "INSTANCE")
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.VOLATILE)
                .build();

        TypeSpec viewModelFactory = TypeSpec.classBuilder("ViewModelFactory")

                .addModifiers(Modifier.PUBLIC)
                .addMethod(constructor)
                .addMethod(initInstance)
                .addMethod(createM
                        .addStatement(" throw new IllegalArgumentException(\"Unknown ViewModel class: \" + className.getName())")
                        .build())
                .addMethods(methodSpecList)
                .addField(instance)
                .addField(mApplication)
                .superclass(superinterface)

                .build();


        javaFile = JavaFile.builder(packageName, viewModelFactory)
                .addFileComment("ViewModelFactory  auto generate ,do not edit")
                //  .addStaticImport(ClassName.bestGuess("android.arch.lifecycle.ViewModelPrfdgjfdljgoviders"))
                .build();//创建文件

        try {
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.print(e.getMessage());
        }
    }

}
